﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;

namespace WindowsPhoneMvp.Binder.PresenterDiscoveryStrategy
{
    public class PresenterBindingTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            var typeString = value as string;
            if(string.IsNullOrEmpty(typeString) == false)
            {
                var val = Type.GetType(typeString, true);
                return new AttachedPropertyPresenterBinding(val);
            }
            return null;
        }
    }

    [TypeConverter(typeof(PresenterBindingTypeConverter))]
    public class AttachedPropertyPresenterBinding
    {
        readonly Type runtimeType;
        public Type RuntimeType { get { return runtimeType; } }

        public AttachedPropertyPresenterBinding(Type runtimeType)
        {
            this.runtimeType = runtimeType;
        }
    }

    public static class Presenter
    {
        public static readonly DependencyProperty TypeProperty = DependencyProperty.RegisterAttached("Type", typeof(AttachedPropertyPresenterBinding), typeof(Presenter), new PropertyMetadata(null));

        public static AttachedPropertyPresenterBinding GetType(UIElement element)
        {
            var type = element.GetValue(TypeProperty) as AttachedPropertyPresenterBinding;
            if(type != null)
            {
                return type;
            }
            return null;
        }

        public static void SetType(UIElement element, AttachedPropertyPresenterBinding value)
        {
            element.SetValue(TypeProperty, value);
        }
    }

    public class AttachedPropertyPresenterDiscoveryStrategy : IPresenterDiscoveryStrategy
    {
        public IEnumerable<PresenterDiscoveryResult> GetBindings(IEnumerable<object> hosts,
            IEnumerable<IView> viewInstances)
        {
            if(hosts == null)
                throw new ArgumentNullException("hosts");

            if(viewInstances == null)
                throw new ArgumentNullException("viewInstances");

            var pendingViewInstances = viewInstances.ToList();

            while(pendingViewInstances.Any())
            {
                var messages = new List<string>();
                var bindings = new List<PresenterBinding>();
                var viewInstance = pendingViewInstances.First();

                var viewType = viewInstance.GetType();

                if(viewInstance is UIElement)
                {
                    var viewDefinedPresenters = Presenter.GetType(viewInstance as UIElement);

                    if(viewDefinedPresenters == null)
                    {
                        messages.Add(string.Format(
                            CultureInfo.InvariantCulture,
                            "could not found a [Presenter.Type] attribute on view XAML {0}",
                            viewType.FullName));
                    }
                    else
                    {
                        var binding = new PresenterBinding(viewDefinedPresenters.RuntimeType, viewInstance.GetType(),
                        BindingMode.Default, new[] { viewInstance });

                        messages.Add(string.Format(
                            CultureInfo.InvariantCulture,
                            "found a [Presenter.Type] attribute on view instance {0} (presenter type: {1}, view type: {2}, binding mode: {3})",
                            viewType.FullName,
                            binding.PresenterType.FullName,
                            binding.ViewType.FullName,
                            binding.BindingMode
                                         ));
                        bindings.Add(binding);
                    }
                }

                var hostDefinedAttributes = hosts
                    .Where(x => !(x is IView && viewInstances.Contains((IView)x)) && !(x is IView))
                    .Select(x => Presenter.GetType(x as UIElement))
                    .Select(h => 
                        new PresenterBinding(h.RuntimeType, viewInstance.GetType(),
                        BindingMode.Default, new[] { viewInstance }));

                var relevantHostDefinedAttributes = hostDefinedAttributes
                    .Where(a => a.ViewType.IsAssignableFrom(viewType));

                foreach(var presenterBinding in relevantHostDefinedAttributes)
                {
                    if(!presenterBinding.ViewType.IsAssignableFrom(viewType))
                    {
                        messages.Add(string.Format(
                            CultureInfo.InvariantCulture,
                            "found, but ignored, a [Presenter.Type] attribute on host instance {0} (presenter type: {1}, view type: {2}, binding mode: {3}) because the view type on the attribute is not compatible with the type of the view instance",
                            presenterBinding.GetType().FullName,
                            presenterBinding.PresenterType.FullName,
                            presenterBinding.ViewType.FullName,
                            presenterBinding.BindingMode
                        ));
                        continue;
                    }

                    messages.Add(string.Format(
                        CultureInfo.InvariantCulture,
                        "found a PresenterBinding on host instance {0} (presenter type: {1}, view type: {2}, binding mode: {3})",
                        presenterBinding.GetType().FullName,
                        presenterBinding.PresenterType.FullName,
                        presenterBinding.ViewType.FullName,
                        presenterBinding.BindingMode
                    ));
                    bindings.Add(presenterBinding);
                }

                yield return new PresenterDiscoveryResult(
                    new[] { viewInstance },
                    "AttachedPropertyPresenterDiscoveryStrategy:\r\n" +
                        string.Join("\r\n", messages.Select(m => "- " + m).ToArray()),
                    bindings
                );

                pendingViewInstances.Remove(viewInstance);
            }
        }
    }
}
